# This file is part of trytond-pcw-stock module for Tryton. 
# The COPYRIGHT file at the top level of this repository contains 
# the full copyright notices and license terms.

from trytond.model import Workflow, ModelView, ModelSQL, fields
from trytond.pyson import If, Not, Equal, Eval, Or, Bool
from trytond.transaction import Transaction
from trytond.pool import Pool, PoolMeta
import math
from decimal import Decimal

__all__ = ['Inventory', 'InventoryLine']
__metaclass__ = PoolMeta

class Inventory(Workflow, ModelSQL, ModelView):
    'Pcw Stock Inventory'
    __name__ = 'pcw.stock.inventory'

    name = fields.Char('Inventory No.', size=None, readonly=True, select=True)
    date = fields.Date('Date', required=True, select=True, 
        states={
            'readonly': Or(Not(Equal(Eval('state'), 'draft')),
                Bool(Eval('lines'))),
            },
        depends=['state', 'lines'])
    description = fields.Char('Descripcion', size=None, 
        states={'readonly': Eval('state') != 'draft',},
        depends=['state'])
    lines = fields.One2Many('pcw.stock.inventory.line', 'inventory', 'Lines', 
        states={'readonly': Eval('state') != 'draft',},
        depends=['state'],
        on_change=['lines', 'state', 'currency','total_lines'])
    total_lines = fields.Function(fields.Numeric('Total', 
        digits=(16, 4)),'get_total_lines')
    total_lines_cache = fields.Numeric('Total',
        digits=(16, 4), readonly=True)
    company = fields.Many2One('company.company', 'Company', required=True,
        states={
            'readonly': True,
            },
        depends=['state', 'lines'])
    currency = fields.Many2One('currency.currency', 'Currency',
        states={
            'required': True,
            'readonly': True,
            },
        depends=['state'])
    state = fields.Selection([
        ('draft', 'Draft'),
        ('done', 'Done'),
        ('cancel', 'Canceled'),
        ], 'State', readonly=True, select=True)


    @classmethod
    def __setup__(cls):
        super(Inventory, cls).__setup__()
        cls._order.insert(0, ('date', 'DESC'))
        cls._order.insert(1, ('name', 'DESC'))

        cls._sql_constraints = [
            ('name_uniq', 'unique(name, company)', 'Inventory number must be unique per Company!'),
        ]

        cls._error_messages.update({
                'delete_cancel': ('Inventory "%s" must be cancelled before '
                    'deletion.'),
                'inventory_exists': ('Inventory with date > "%s" exists.'),
                })

        cls._transitions |= set((
                ('draft', 'done'),
                ('done', 'cancel'),
                ('cancel', 'draft'),
                ))
        cls._buttons.update({
                'cancel': {
                    'invisible': Eval('state').in_(['cancel', 'draft']),
                    },
                'draft': {
                    'invisible': ~Eval('state').in_(['cancel']),
                    'icon': If(Eval('state') == 'cancel', 'tryton-clear','tryton-go-previous'),
                    },
                'confirm': {
                    'invisible': Eval('state').in_(['done', 'cancel']),
                    },
                'update_diff': {
                    'readonly': Eval('state') != 'draft',
                    },
                })

        # The states where amounts are cached
        cls._states_cached = ['done', 'cancel']

    @staticmethod
    def default_state():
        return 'draft'

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @staticmethod
    def default_currency():
        Company = Pool().get('company.company')
        company = Transaction().context.get('company')
        if company:
            return Company(company).currency.id

    @staticmethod
    def inventory_exists(date, company_id, ignore_id=None, sign=">"): 
        Ss = Pool().get('pcw.stock.inventory')
        search_list=[('date', sign, date), ('state', '=', 'done'), ('company','=',company_id)]
        if ignore_id:           
            search_list.append(('id','!=',ignore_id))
            if sign == ">=":
                search_list.append(('id','>',ignore_id))

        orders = Ss.search(search_list, order=[('date','DESC')], limit=1)

        if orders:
            return True
        return False 

    @classmethod
    def set_name(cls, orders):
        '''
        Fill the name field with the pcw inventory sequence
        '''
        pool = Pool()
        Sequence = pool.get('ir.sequence')
        Config = pool.get('pcw.stock.configuration')

        config = Config(1)
        for order in orders:
            if order.name:
                continue
            name = Sequence.get_id(config.inventory_sequence.id)
            cls.write([order], {
                    'name': name,
                    })

    @classmethod
    def delete(cls, orders):
        for order in orders:
            if order.state in ['done',]:
                cls.raise_user_error('delete_cancel', error_args=(str(order.name),))                
        super(Inventory, cls).delete(orders)

    def on_change_lines(self):
        res = {}
        if not self.lines:
            res['total_lines'] = 0
            return res
        res['total_lines'] = self.get_total_lines()
        return res

    def get_total_lines(self, name=None):
        if (self.state in self._states_cached
                and self.total_lines_cache is not None):
            return self.total_lines_cache
        return self.get_line_total()

    def get_line_total(self):
        amount = Decimal(0)
        for line in self.lines:              
            if line.diff_quantity and line.diff_quantity != 0:
                amount = amount + (Decimal(line.diff_quantity) * line.average_cost)
        return Decimal('%.4f' % amount)

    @classmethod
    def store_cache(cls, orders):
        for order in orders:
            cls.write([order], {
                    'total_lines_cache': order.total_lines,
                    })

    @classmethod
    @ModelView.button
    @Workflow.transition('done')
    def confirm(cls, inventories):
        Move = Pool().get('pcw.stock.move')
        Inv = Pool().get('pcw.stock.inventory')

        cls.update_diff(inventories)

        moves=[]
        Inv.set_name(inventories)  
        for inventory in inventories:
            if inventory.inventory_exists(inventory.date, inventory.company.id, inventory.id):
                cls.raise_user_error('inventory_exists', error_args=(str(inventory.date),))

            mv = None
            for line in inventory.lines:
                if line.diff_quantity == 0:
                    continue 
                mv = Move()
                mv.date = inventory.date                
                if line.diff_quantity > 0:
                    mv.type = 'in'
                else:
                    mv.type = 'out' 
                mv.reference = inventory.name
                mv.product = line.product
                mv.quantity = math.fabs(line.diff_quantity)
                mv.uom = line.uom_1
                mv.unit_price = line.average_cost
                mv.company = inventory.company
                mv.currency = inventory.currency
                mv.inventory = inventory
                mv.save()
                if mv.id:
                    line.move = mv.id
                    line.save()
                    moves.append(mv)  
        if moves:
            Move.assign(moves)
            Move.confirm(moves)

        cls.store_cache(inventories)

    @classmethod
    @ModelView.button
    @Workflow.transition('cancel')
    def cancel(cls, inventories):
        Move = Pool().get('pcw.stock.move')
        cls.store_cache(inventories)
        for inventory in inventories:
            if inventory.inventory_exists(inventory.date, inventory.company.id, inventory.id):
                cls.raise_user_error('inventory_exists', error_args=(str(inventory.date),))
            for line in inventory.lines:
                if line.move:
                    Move.cancel([line.move])
                    Move.delete([line.move])

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, inventories):
        pass


    @classmethod
    def update_diff(cls, inventories):
        '''
        Update diff for each line
        '''
        for inv in inventories:
            if inv.state == 'done':
                continue
            for line in inv.lines:
                res = line.get_quantities(inv.date, line.product, inv.company, line.qty_1, line.uom_1, 
                        line.qty_2, line.uom_2)
                line.expected_quantity = res['expected_quantity'] 
                line.expected_quantity_show = res['expected_quantity_show'] 
                line.average_cost = res['average_cost'] 
                line.save()


UOM_DOMAIN=[
    If(Bool(Eval('product_uom_category')),
        ('category', '=', Eval('product_uom_category')),
        ('category', '!=', -1)),
]
UOM_STATE={
    'readonly': ~Eval('_parent_inventory'),
}

class InventoryLine(ModelSQL, ModelView):
    'Pcw Stock Inventory Line'
    __name__ = 'pcw.stock.inventory.line'
    _rec_name = 'product'

    inventory = fields.Many2One('pcw.stock.inventory', 'Inventory', required=True,
            ondelete='CASCADE')
    product = fields.Many2One('product.product', 'Product', required=True,
        domain=[
            ('type', '!=', 'service'),
            ],
        on_change=['product', 'qty_1', '_parent_inventory.company', '_parent_inventory.date'])

    qty_1 = fields.Float('Qty 1', required=True, digits=(16, 4), 
        on_change=['product', 'qty_1', 'uom_1', 'qty_2', 'uom_2', '_parent_inventory.company', '_parent_inventory.date'])

    uom_1 = fields.Many2One('product.uom', 'Unit 1', required=True,
        states=UOM_STATE, domain=UOM_DOMAIN,
        on_change=['product', 'qty_1', 'uom_1', 'qty_2', 'uom_2', '_parent_inventory.company', '_parent_inventory.date'],
        depends=['state', 'product_uom_category'])

    qty_2 = fields.Float('Qty 2', digits=(16, 4), 
        on_change=['product', 'qty_1', 'uom_1', 'qty_2', 'uom_2', '_parent_inventory.company', '_parent_inventory.date'])

    uom_2 = fields.Many2One('product.uom', 'Unit 2',
        states=UOM_STATE, domain=UOM_DOMAIN,
        on_change=['product', 'qty_1', 'uom_1', 'qty_2', 'uom_2', '_parent_inventory.company', '_parent_inventory.date'],
        depends=['state', 'product_uom_category'])

    expected_quantity = fields.Float('Expected', required=True, digits=(16,4))
    expected_quantity_show = fields.Function(fields.Float('Expected', digits=(16,4)), 'get_expected_quantity_show')

    average_cost = fields.Numeric('Cost', digits=(16, 4), help="Average Cost")

    product_uom_category = fields.Function(fields.Many2One('product.uom.category', 'Product Uom Category',
            on_change_with=['product']), 'on_change_with_product_uom_category')

    diff_quantity = fields.Function(fields.Numeric('Diff', digits=(16,4)), 'get_diff_quantity')

    move = fields.Many2One('pcw.stock.move', 'Move', readonly=True)


    @classmethod
    def __setup__(cls):
        super(InventoryLine, cls).__setup__()
        cls._sql_constraints = [
            ('product_uniq', 'unique(product, inventory)', 'Product appears more than once in inventory'),
        ]

    def on_change_product(self):
        res = self.get_quantities(self.inventory.date, self.product, 
            self.inventory.company, self.qty_1, None, None, None)
        if self.product:
            res['uom.rec_name'] = self.product.default_uom.rec_name
        return res

    def on_change_qty_1(self):
        return self.get_quantities(self.inventory.date, self.product, 
            self.inventory.company, self.qty_1, self.uom_1, self.qty_2, self.uom_2)

    def on_change_uom_1(self):
        return self.get_quantities(self.inventory.date, self.product, 
            self.inventory.company, self.qty_1, self.uom_1, self.qty_2, self.uom_2)

    def on_change_qty_2(self):
        return self.get_quantities(self.inventory.date, self.product, 
            self.inventory.company, self.qty_1, self.uom_1, self.qty_2, self.uom_2)

    def on_change_uom_2(self):
        return self.get_quantities(self.inventory.date, self.product, 
            self.inventory.company, self.qty_1, self.uom_1, self.qty_2, self.uom_2)

    def on_change_with_product_uom_category(self, name=None):
        if self.product:
            return self.product.default_uom_category.id

    def get_diff_quantity(self, name):
        return (self.qty_1 + self.get_quantity_2()) - self.get_expected_quantity_show()

    def get_expected_quantity_show(self, name=None):
        obj_uom = Pool().get('product.uom')
        if not self.expected_quantity:
            return 0
        if self.uom_1.id == self.product.default_uom.id:
            return self.expected_quantity
        obj_uom = Pool().get('product.uom')
        return obj_uom.compute_qty(self.product.default_uom, self.expected_quantity, self.uom_1)

    def get_quantity_2(self):
        if not self.qty_2 or not self.uom_2:
            return 0 
        if self.uom_1.id == self.uom_2.id:
            return self.qty_2
        obj_uom = Pool().get('product.uom')
        return obj_uom.compute_qty(self.uom_2, self.qty_2, self.uom_1)

    def get_quantities(self, date, product, company, qty_1, uom_1, qty_2, uom_2):
        obj_uom = Pool().get('product.uom')
        res = {}

        if not product:
            res['qty_1'] = None
            res['uom_1'] = None
            res['qty_2'] = None
            res['uom_2'] = None
            res['expected_quantity'] = 0.0
            res['expected_quantity_show'] = 0.0
            res['average_cost'] = 0.0
            res['diff_quantity'] = 0.0
            return res 
         
        if uom_1:
            uom_compute = obj_uom(uom_1.id)
        else:
            if product.uom_count:
                res['uom_1'] = product.uom_count.id
                uom_compute = obj_uom(product.uom_count.id) 
            else:
                res['uom_1'] = product.default_uom.id
                uom_compute = obj_uom(product.default_uom.id) 

        existencia = product.get_quantity_by_date(date_end=date)
        res['expected_quantity'] = Decimal('%.4f' % existencia) 
        res['expected_quantity_show'] = Decimal('%.4f' % existencia)
        res['diff_quantity'] = Decimal(0.0)

        quantity_2 = 0.0
        if uom_2 and qty_2:
            quantity_2 = obj_uom.compute_qty(uom_2, qty_2, uom_compute)

        res['average_cost'] = Decimal('%.4f' % product.get_average_cost(uom_compute))
        res['expected_quantity_show'] = Decimal('%.4f' % obj_uom.compute_qty(product.default_uom, existencia, uom_compute))
        
        if qty_1 and uom_1:
            res['diff_quantity'] = Decimal(qty_1 + quantity_2) - res['expected_quantity_show']
            res['diff_quantity'] = Decimal('%.4f' % res['diff_quantity'])

        return res


